//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

#pragma once
namespace D3DX12Residency
{
	__declspec(selectany) INT64 g_ResidencyManagerUniqueID = 0;

#if 0
#define RESIDENCY_CHECK(x) \
	if((x) == false) { DebugBreak(); }

#define RESIDENCY_CHECK_RESULT(x) \
	if((x) != S_OK) { DebugBreak(); }
#else
#define RESIDENCY_CHECK(x)
#define RESIDENCY_CHECK_RESULT(x) x
#endif

#define RESIDENCY_SINGLE_THREADED 0

#define RESIDENCY_MIN(x,y) ((x) < (y) ? (x) : (y))
#define RESIDENCY_MAX(x,y) ((x) > (y) ? (x) : (y))

	// This size can be tuned to your app in order to save space
#define MAX_NUM_CONCURRENT_CMD_LISTS 32

	namespace Internal
	{
		class CriticalSection
		{
			friend class ScopedLock;
		public:
			CriticalSection()
			{
				InitializeCriticalSectionAndSpinCount(&CS, 8);
			}

			~CriticalSection()
			{
				DeleteCriticalSection(&CS);
			}

		private:
			CRITICAL_SECTION CS;
		};

		class ScopedLock
		{
		public:

			ScopedLock() : pCS(nullptr) {};
			ScopedLock(CriticalSection* pCSIn) : pCS(pCSIn)
			{
				if (pCS)
				{
					EnterCriticalSection(&pCS->CS);
				}
			};

			~ScopedLock()
			{
				if (pCS)
				{
					LeaveCriticalSection(&pCS->CS);
				}
			}

		private:

			CriticalSection* pCS;
		};

		// One per Residency Manager
		class SyncManager
		{
		public:
			SyncManager()
			{
				for (UINT32 i = 0; i < ARRAYSIZE(AvailableCommandLists); i++)
				{
					AvailableCommandLists[i] = false;
				}
			}

			Internal::CriticalSection MaskCriticalSection;

			static const UINT32 sUnsetValue = UINT32(-1);
			// Represents which command lists are currently open for recording
			bool AvailableCommandLists[MAX_NUM_CONCURRENT_CMD_LISTS];
		};

		//Forward Declaration
		class ResidencyManagerInternal;
	}

	// Used to track meta data for each object the app potentially wants
	// to make resident or evict.
	class ManagedObject
	{
	public:
		enum class RESIDENCY_STATUS
		{
			RESIDENT,
			EVICTED
		};

		ManagedObject() :
			pUnderlying(nullptr),
			Size(0),
			ResidencyStatus(RESIDENCY_STATUS::RESIDENT),
			LastGPUSyncPoint(0),
			LastUsedTimestamp(0)
		{
			memset(CommandListsUsedOn, 0, sizeof(CommandListsUsedOn));
		}

		void Initialize(ID3D12Pageable* pUnderlyingIn, UINT64 ObjectSize, UINT64 InitialGPUSyncPoint = 0)
		{
			RESIDENCY_CHECK(pUnderlying == nullptr);
			pUnderlying = pUnderlyingIn;
			Size = ObjectSize;
			LastGPUSyncPoint = InitialGPUSyncPoint;
		}

		inline bool IsInitialized() { return pUnderlying != nullptr; }

		// Wether the object is resident or not
		RESIDENCY_STATUS ResidencyStatus;

		// The underlying D3D Object being tracked
		ID3D12Pageable* pUnderlying;
		// The size of the D3D Object in bytes
		UINT64 Size;

		UINT64 LastGPUSyncPoint;
		UINT64 LastUsedTimestamp;

		// This is used to track which open command lists this resource is currently used on.
		bool CommandListsUsedOn[MAX_NUM_CONCURRENT_CMD_LISTS];

		// Linked list entry
		LIST_ENTRY ListEntry;
	};

	// This represents a set of objects which are referenced by a command list i.e. every time a resource
	// is bound for rendering, clearing, copy etc. the set must be updated to ensure the it is resident 
	// for execution.
	class ResidencySet
	{
		friend class ResidencyManager;
		friend class Internal::ResidencyManagerInternal;
	public:

		static const UINT32 InvalidIndex = (UINT32)-1;

		ResidencySet() :
			CommandListIndex(InvalidIndex),
			MaxResidencySetSize(0),
			CurrentSetSize(0),
			ppSet(nullptr),
			IsOpen(false),
			OutOfMemory(false),
			pSyncManager(nullptr)
		{
		};

		~ResidencySet()
		{
			delete[](ppSet);
		}

		// Returns true if the object was inserted, false otherwise
		inline bool Insert(ManagedObject* pObject)
		{
			RESIDENCY_CHECK(IsOpen);
			RESIDENCY_CHECK(CommandListIndex != InvalidIndex);

			// If we haven't seen this object on this command list mark it
			if (pObject->CommandListsUsedOn[CommandListIndex] == false)
			{
				pObject->CommandListsUsedOn[CommandListIndex] = true;
				if (ppSet == nullptr || CurrentSetSize >= MaxResidencySetSize)
				{
					Realloc();
				}
				if (ppSet == nullptr)
				{
					OutOfMemory = true;
					return false;
				}

				ppSet[CurrentSetSize++] = pObject;

				return true;
			}
			else
			{
				return false;
			}
		}

		HRESULT Open()
		{
			Internal::ScopedLock Lock(&pSyncManager->MaskCriticalSection);

			// It's invalid to open a set that is already open
			if (IsOpen)
			{
				return E_INVALIDARG;
			}

			RESIDENCY_CHECK(CommandListIndex == InvalidIndex);

			bool CommandlistAvailable = false;
			// Find the first available command list by bitscanning
			for (UINT32 i = 0; i < ARRAYSIZE(pSyncManager->AvailableCommandLists); i++)
			{
				if (pSyncManager->AvailableCommandLists[i] == false)
				{
					CommandListIndex = i;
					pSyncManager->AvailableCommandLists[i] = true;
					CommandlistAvailable = true;
					break;
				}
			}

			if (CommandlistAvailable == false)
			{
				// There are too many open residency sets, consider using less or increasing the value of MAX_NUM_CONCURRENT_CMD_LISTS
				RESIDENCY_CHECK(false);
				return E_OUTOFMEMORY;
			}

			CurrentSetSize = 0;

			IsOpen = true;
			OutOfMemory = false;
			return S_OK;
		}

		HRESULT Close()
		{
			if (IsOpen == false)
			{
				return E_INVALIDARG;
			}

			if (OutOfMemory == true)
			{
				return E_OUTOFMEMORY;
			}

			for (INT32 i = 0; i < CurrentSetSize; i++)
			{
				Remove(ppSet[i]);
			}

			ReturnCommandListReservation();

			IsOpen = false;

			return S_OK;
		}

	private:

		inline void Remove(ManagedObject* pObject)
		{
			pObject->CommandListsUsedOn[CommandListIndex] = false;
		}

		inline void ReturnCommandListReservation()
		{
			Internal::ScopedLock Lock(&pSyncManager->MaskCriticalSection);

			pSyncManager->AvailableCommandLists[CommandListIndex] = false;

			CommandListIndex = ResidencySet::InvalidIndex;

			IsOpen = false;
		}

		void Initialize(Internal::SyncManager* pSyncManagerIn)
		{
			pSyncManager = pSyncManagerIn;
		}

		bool Initialize(Internal::SyncManager* pSyncManagerIn, UINT32 MaxSize)
		{
			pSyncManager = pSyncManagerIn;
			MaxResidencySetSize = MaxSize;

			ppSet = new ManagedObject*[MaxResidencySetSize];

			return ppSet != nullptr;
		}

		inline void Realloc()
		{
			MaxResidencySetSize = (MaxResidencySetSize == 0) ? 4096 : INT32(MaxResidencySetSize + (MaxResidencySetSize / 2.0f));
			ManagedObject** ppNewAlloc = new ManagedObject*[MaxResidencySetSize];

			if (ppSet && ppNewAlloc)
			{
				memcpy(ppNewAlloc, ppSet, CurrentSetSize * sizeof(ManagedObject*));
				delete[](ppSet);
			}

			ppSet = ppNewAlloc;
		}

		UINT32 CommandListIndex;

		ManagedObject** ppSet;
		INT32 MaxResidencySetSize;
		INT32 CurrentSetSize;

		bool IsOpen;
		bool OutOfMemory;

		Internal::SyncManager* pSyncManager;
	};

	namespace Internal
	{
		/* List Helpers */
		inline void InitializeListHead(LIST_ENTRY* pHead)
		{
			pHead->Flink = pHead->Blink = pHead;
		}

		inline void InsertHeadList(LIST_ENTRY* pHead, LIST_ENTRY* pEntry)
		{
			pEntry->Blink = pHead;
			pEntry->Flink = pHead->Flink;

			pHead->Flink->Blink = pEntry;
			pHead->Flink = pEntry;
		}

		inline void InsertTailList(LIST_ENTRY* pHead, LIST_ENTRY* pEntry)
		{
			pEntry->Flink = pHead;
			pEntry->Blink = pHead->Blink;

			pHead->Blink->Flink = pEntry;
			pHead->Blink = pEntry;
		}

		inline void RemoveEntryList(LIST_ENTRY* pEntry)
		{
			pEntry->Blink->Flink = pEntry->Flink;
			pEntry->Flink->Blink = pEntry->Blink;
		}

		inline LIST_ENTRY* RemoveHeadList(LIST_ENTRY* pHead)
		{
			LIST_ENTRY* pEntry = pHead->Flink;
			Internal::RemoveEntryList(pEntry);
			return pEntry;
		}

		inline LIST_ENTRY* RemoveTailList(LIST_ENTRY* pHead)
		{
			LIST_ENTRY* pEntry = pHead->Blink;
			Internal::RemoveEntryList(pEntry);
			return pEntry;
		}

		inline bool IsListEmpty(LIST_ENTRY* pEntry)
		{
			return pEntry->Flink == pEntry;
		}

		struct Fence
		{
			Fence(UINT64 StartingValue) : pFence(nullptr), FenceValue(StartingValue)
			{
				Internal::InitializeListHead(&ListEntry);
			};

			HRESULT Initialize(ID3D12Device* pDevice)
			{
				HRESULT hr = pDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pFence));
				RESIDENCY_CHECK_RESULT(hr);

				return hr;
			}

			void Destroy()
			{
				if (pFence)
				{
					pFence->Release();
					pFence = nullptr;
				}
			}

			HRESULT GPUWait(ID3D12CommandQueue* pQueue)
			{
				HRESULT hr = pQueue->Wait(pFence, FenceValue);
				RESIDENCY_CHECK_RESULT(hr);
				return hr;
			}

			HRESULT GPUSignal(ID3D12CommandQueue* pQueue)
			{
				HRESULT hr = pQueue->Signal(pFence, FenceValue);
				RESIDENCY_CHECK_RESULT(hr);
				return hr;
			}

			inline void Increment()
			{
				FenceValue++;
			}

			ID3D12Fence* pFence;
			UINT64 FenceValue;
			LIST_ENTRY ListEntry;
		};

		// Represents a time on a particular queue that a resource was used
		struct QueueSyncPoint
		{
			QueueSyncPoint() : pFence(nullptr), LastUsedValue(0) {};

			inline bool IsCompleted() { return LastUsedValue <= pFence->pFence->GetCompletedValue(); }

			inline void WaitForCompletion(HANDLE Event)
			{
				RESIDENCY_CHECK_RESULT(pFence->pFence->SetEventOnCompletion(LastUsedValue, Event));
				RESIDENCY_CHECK_RESULT(WaitForSingleObject(Event, INFINITE));
			}

			Fence* pFence;
			UINT64 LastUsedValue;
		};

		struct DeviceWideSyncPoint
		{
			DeviceWideSyncPoint(UINT32 NumQueues, UINT64 Generation) :
				GenerationID(Generation), NumQueueSyncPoints(NumQueues) {};

			// Create the whole structure in one allocation for locality
			static DeviceWideSyncPoint* CreateSyncPoint(UINT32 NumQueues, UINT64 Generation)
			{
				DeviceWideSyncPoint* pSyncPoint = nullptr;
				const SIZE_T Size = sizeof(DeviceWideSyncPoint) + (sizeof(QueueSyncPoint) * (NumQueues - 1));

				BYTE* pAlloc = new BYTE[Size];
				if (pAlloc && Size >= sizeof(DeviceWideSyncPoint))
				{
					pSyncPoint = new (pAlloc) DeviceWideSyncPoint(NumQueues, Generation);
				}

				return pSyncPoint;
			}

			// A device wide fence is completed if all of the queues that were active at that point are completed
			inline bool IsCompleted()
			{
				for (UINT32 i = 0; i < NumQueueSyncPoints; i++)
				{
					if (pQueueSyncPoints[i].IsCompleted() == false)
					{
						return false;
					}
				}
				return true;
			}

			inline void WaitForCompletion(HANDLE Event)
			{
				for (UINT32 i = 0; i < NumQueueSyncPoints; i++)
				{
					if (pQueueSyncPoints[i].IsCompleted() == false)
					{
						pQueueSyncPoints[i].WaitForCompletion(Event);
					}
				}
			}

			const UINT64 GenerationID;
			const UINT32 NumQueueSyncPoints;
			LIST_ENTRY ListEntry;
			// NumQueueSyncPoints QueueSyncPoints will be placed below here
			QueueSyncPoint pQueueSyncPoints[1];
		};

		// A Least Recently Used Cache. Tracks all of the objects requested by the app so that objects
		// that aren't used freqently can get evicted to help the app stay under buget.
		class LRUCache
		{
		public:
			LRUCache() :
				NumResidentObjects(0),
				NumEvictedObjects(0),
				ResidentSize(0)
			{
				Internal::InitializeListHead(&ResidentObjectListHead);
				Internal::InitializeListHead(&EvictedObjectListHead);
			};

			void Insert(ManagedObject* pObject)
			{
				if (pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT)
				{
					Internal::InsertHeadList(&ResidentObjectListHead, &pObject->ListEntry);
					NumResidentObjects++;
					ResidentSize += pObject->Size;
				}
				else
				{
					Internal::InsertHeadList(&EvictedObjectListHead, &pObject->ListEntry);
					NumEvictedObjects++;
				}
			}

			void Remove(ManagedObject* pObject)
			{
				Internal::RemoveEntryList(&pObject->ListEntry);
				if (pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT)
				{
					NumResidentObjects--;
					ResidentSize -= pObject->Size;
				}
				else
				{
					NumEvictedObjects--;
				}
			}

			// When an object is used by the GPU we move it to the end of the list.
			// This way things closer to the head of the list are the objects which
			// are stale and better candidates for eviction
			void ObjectReferenced(ManagedObject* pObject)
			{
				RESIDENCY_CHECK(pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT);

				Internal::RemoveEntryList(&pObject->ListEntry);
				Internal::InsertTailList(&ResidentObjectListHead, &pObject->ListEntry);
			}

			void MakeResident(ManagedObject* pObject)
			{
				RESIDENCY_CHECK(pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::EVICTED);

				pObject->ResidencyStatus = ManagedObject::RESIDENCY_STATUS::RESIDENT;
				Internal::RemoveEntryList(&pObject->ListEntry);
				Internal::InsertTailList(&ResidentObjectListHead, &pObject->ListEntry);

				NumEvictedObjects--;
				NumResidentObjects++;
				ResidentSize += pObject->Size;
			}

			void Evict(ManagedObject* pObject)
			{
				RESIDENCY_CHECK(pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT);

				pObject->ResidencyStatus = ManagedObject::RESIDENCY_STATUS::EVICTED;
				Internal::RemoveEntryList(&pObject->ListEntry);
				Internal::InsertTailList(&EvictedObjectListHead, &pObject->ListEntry);

				NumResidentObjects--;
				ResidentSize -= pObject->Size;
				NumEvictedObjects++;
			}

			// Evict all of the resident objects used in sync points up to the specficied one (inclusive)
			void TrimToSyncPointInclusive(INT64 CurrentUsage, INT64 CurrentBudget, ID3D12Pageable** EvictionList, UINT32& NumObjectsToEvict, UINT64 SyncPoint)
			{
				NumObjectsToEvict = 0;

				LIST_ENTRY* pResourceEntry = ResidentObjectListHead.Flink;
				while (pResourceEntry != &ResidentObjectListHead)
				{
					ManagedObject* pObject = CONTAINING_RECORD(pResourceEntry, ManagedObject, ListEntry);

					if (pObject->LastGPUSyncPoint > SyncPoint || CurrentUsage < CurrentBudget)
					{
						break;
					}

					RESIDENCY_CHECK(pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT);

					EvictionList[NumObjectsToEvict++] = pObject->pUnderlying;
					Evict(pObject);

					CurrentUsage -= pObject->Size;

					pResourceEntry = ResidentObjectListHead.Flink;
				}
			}

			// Trim all objects which are older than the specified time
			void TrimAgedAllocations(DeviceWideSyncPoint* MaxSyncPoint, ID3D12Pageable** EvictionList, UINT32& NumObjectsToEvict, UINT64 CurrentTimeStamp, UINT64 MinDelta)
			{
				LIST_ENTRY* pResourceEntry = ResidentObjectListHead.Flink;
				while (pResourceEntry != &ResidentObjectListHead)
				{
					ManagedObject* pObject = CONTAINING_RECORD(pResourceEntry, ManagedObject, ListEntry);

					if ((MaxSyncPoint && pObject->LastGPUSyncPoint >= MaxSyncPoint->GenerationID) || // Only trim allocations done on the GPU
						CurrentTimeStamp - pObject->LastUsedTimestamp <= MinDelta) // Don't evict things which have been used recently
					{
						break;
					}

					RESIDENCY_CHECK(pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::RESIDENT);
					EvictionList[NumObjectsToEvict++] = pObject->pUnderlying;
					Evict(pObject);

					pResourceEntry = ResidentObjectListHead.Flink;
				}
			}

			ManagedObject* GetResidentListHead()
			{
				if (IsListEmpty(&ResidentObjectListHead))
				{
					return nullptr;
				}
				return CONTAINING_RECORD(ResidentObjectListHead.Flink, ManagedObject, ListEntry);
			}

			LIST_ENTRY ResidentObjectListHead;
			LIST_ENTRY EvictedObjectListHead;

			UINT32 NumResidentObjects;
			UINT32 NumEvictedObjects;

			UINT64 ResidentSize;
		};

		class ResidencyManagerInternal
		{
		public:
			ResidencyManagerInternal(SyncManager* pSyncManagerIn) :
				Device(nullptr),
				AsyncThreadFence(1),
				CompletionEvent(INVALID_HANDLE_VALUE),
				AsyncThreadWorkCompletionEvent(INVALID_HANDLE_VALUE),
				Adapter(nullptr),
				AsyncWorkEvent(INVALID_HANDLE_VALUE),
				AsyncWorkThread(INVALID_HANDLE_VALUE),
				FinishAsyncWork(false),
				cStartEvicted(false),
				CurrentSyncPointGeneration(0),
				NumQueuesSeen(0),
				NodeIndex(0),
				CurrentAsyncWorkloadHead(0),
				CurrentAsyncWorkloadTail(0),
				cMinEvictionGracePeriod(1.0f),
				cMaxEvictionGracePeriod(60.0f),
				cTrimPercentageMemoryUsageThreshold(0.7f),
				AsyncWorkQueue(nullptr),
				MaxSoftwareQueueLatency(6),
				AsyncWorkQueueSize(7),
				pSyncManager(pSyncManagerIn)
			{
				Internal::InitializeListHead(&QueueFencesListHead);
				Internal::InitializeListHead(&InFlightSyncPointsHead);

				ResidencyManagerUniqueID = InterlockedIncrement64(&g_ResidencyManagerUniqueID);
			};

			// NOTE: DeviceNodeIndex is an index not a mask. The majority of D3D12 uses bit masks to identify a GPU node whereas DXGI uses 0 based indices.
			HRESULT Initialize(ID3D12Device* ParentDevice, UINT DeviceNodeIndex, IDXGIAdapter3* ParentAdapter, UINT32 MaxLatency)
			{
				Device = ParentDevice;
				NodeIndex = DeviceNodeIndex;
				Adapter = ParentAdapter;
				MaxSoftwareQueueLatency = MaxLatency;

				AsyncWorkQueueSize = MaxLatency + 1;
				AsyncWorkQueue = new AsyncWorkload[AsyncWorkQueueSize];

				if (AsyncWorkQueue == nullptr)
				{
					return E_OUTOFMEMORY;
				}

				LARGE_INTEGER Frequency;
				QueryPerformanceFrequency(&Frequency);

				// Calculate how many QPC ticks are equivalent to the given time in seconds
				MinEvictionGracePeriodTicks = UINT64(Frequency.QuadPart * cMinEvictionGracePeriod);
				MaxEvictionGracePeriodTicks = UINT64(Frequency.QuadPart * cMaxEvictionGracePeriod);

				HRESULT hr = S_OK;
				hr = AsyncThreadFence.Initialize(Device);

				if (SUCCEEDED(hr))
				{
					CompletionEvent = CreateEvent(nullptr, false, false, nullptr);
					if (CompletionEvent == INVALID_HANDLE_VALUE)
					{
						hr = HRESULT_FROM_WIN32(GetLastError());
					}
				}

				if (SUCCEEDED(hr))
				{
					AsyncThreadWorkCompletionEvent = CreateEvent(nullptr, false, false, nullptr);
					if (AsyncThreadWorkCompletionEvent == INVALID_HANDLE_VALUE)
					{
						hr = HRESULT_FROM_WIN32(GetLastError());
					}
				}

				if (SUCCEEDED(hr))
				{
					AsyncWorkEvent = CreateEvent(nullptr, true, false, nullptr);
					if (AsyncWorkEvent == INVALID_HANDLE_VALUE)
					{
						hr = HRESULT_FROM_WIN32(GetLastError());
					}
				}

#if !RESIDENCY_SINGLE_THREADED
				if (SUCCEEDED(hr))
				{
					AsyncWorkThread = CreateThread(nullptr, 0, AsyncThreadStart, (void*) this, 0, nullptr);

					if (AsyncWorkThread == INVALID_HANDLE_VALUE)
					{
						hr = HRESULT_FROM_WIN32(GetLastError());
					}
				}
#endif

				return hr;
			}

			void Destroy()
			{
				AsyncThreadFence.Destroy();

				if (CompletionEvent != INVALID_HANDLE_VALUE)
				{
					CloseHandle(CompletionEvent);
					CompletionEvent = INVALID_HANDLE_VALUE;
				}

#if !RESIDENCY_SINGLE_THREADED
				AsyncWorkload* pWork = DequeueAsyncWork();

				while (pWork)
				{
					pWork = DequeueAsyncWork();
				}

				FinishAsyncWork = true;
				if (SetEvent(AsyncWorkEvent) == false)
				{
					RESIDENCY_CHECK_RESULT(HRESULT_FROM_WIN32(GetLastError()));
				}

				// Make sure the async worker thread is finished to prevent dereferencing
				// dangling pointers to ResidencyManagerInternal
				WaitForSingleObject(AsyncWorkThread, INFINITE);
				if (AsyncWorkThread != INVALID_HANDLE_VALUE)
				{
					CloseHandle(AsyncWorkThread);
					AsyncWorkThread = INVALID_HANDLE_VALUE;
				}

				if (AsyncWorkEvent != INVALID_HANDLE_VALUE)
				{
					CloseHandle(AsyncWorkEvent);
					AsyncWorkEvent = INVALID_HANDLE_VALUE;
				}
#endif

				if (AsyncThreadWorkCompletionEvent != INVALID_HANDLE_VALUE)
				{
					CloseHandle(AsyncThreadWorkCompletionEvent);
					AsyncThreadWorkCompletionEvent = INVALID_HANDLE_VALUE;
				}

				while (Internal::IsListEmpty(&QueueFencesListHead) == false)
				{
					Internal::Fence* pObject =
						CONTAINING_RECORD(QueueFencesListHead.Flink, Internal::Fence, ListEntry);

					pObject->Destroy();
					Internal::RemoveHeadList(&QueueFencesListHead);
					delete(pObject);
				}
			}

			void BeginTrackingObject(ManagedObject* pObject)
			{
				Internal::ScopedLock Lock(&Mutex);

				if (pObject)
				{
					RESIDENCY_CHECK(pObject->pUnderlying != nullptr);
					if (cStartEvicted)
					{
						pObject->ResidencyStatus = ManagedObject::RESIDENCY_STATUS::EVICTED;
						RESIDENCY_CHECK_RESULT(Device->Evict(1, &pObject->pUnderlying));
					}

					LRU.Insert(pObject);
				}
			}

			void EndTrackingObject(ManagedObject* pObject)
			{
				Internal::ScopedLock Lock(&Mutex);

				LRU.Remove(pObject);
			}

			// One residency set per command-list
			HRESULT ExecuteCommandLists(ID3D12CommandQueue* Queue, ID3D12CommandList** CommandLists, ResidencySet** ResidencySets, UINT32 Count)
			{
				return ExecuteSubset(Queue, CommandLists, ResidencySets, Count);
			}

			HRESULT GetCurrentGPUSyncPoint(ID3D12CommandQueue* Queue, UINT64 *pGPUSyncPoint)
			{
				Internal::Fence* QueueFence = nullptr;
				HRESULT hr = GetFence(Queue, QueueFence);

				// The signal and increment need to be atomic
				if(SUCCEEDED(hr))
				{
					Internal::ScopedLock Lock(&ExecutionCS);
					*pGPUSyncPoint = QueueFence->FenceValue;
					hr = SignalFence(Queue, QueueFence);
				}
				return hr;
			}

		private:
			HRESULT GetFence(ID3D12CommandQueue *Queue, Internal::Fence *&QueueFence)
			{
				// We have to track each object on each queue so we know when it is safe to evict them. Therefore, for every queue that we
				// see, associate a fence with it
				GUID FenceGuid = { 0xf0, 0, 0xd, { 0, 0, 0, 0, 0, 0, 0, 0 } };

				// Generate a GUID based on this queue
				memcpy((void*)FenceGuid.Data4, Queue, sizeof(ID3D12CommandQueue*));

				QueueFence = nullptr;
				HRESULT hr = S_OK;

				struct
				{
					Internal::Fence* pFence;
					INT64 ResidencyManagerUniqueID;
				} CommandQueuePrivateData;

				// Find or create the fence for this queue
				{
					UINT32 Size = sizeof(CommandQueuePrivateData);
					hr = Queue->GetPrivateData(FenceGuid, &Size, &CommandQueuePrivateData);
					if (FAILED(hr) || ResidencyManagerUniqueID != CommandQueuePrivateData.ResidencyManagerUniqueID)
					{
						QueueFence = new Internal::Fence(1);
						hr = QueueFence->Initialize(Device);
						Internal::InsertTailList(&QueueFencesListHead, &QueueFence->ListEntry);

						InterlockedIncrement(&NumQueuesSeen);

						if (SUCCEEDED(hr))
						{
							CommandQueuePrivateData = { QueueFence, ResidencyManagerUniqueID };
							hr = Queue->SetPrivateData(FenceGuid, UINT32(sizeof(CommandQueuePrivateData)), &CommandQueuePrivateData);
							RESIDENCY_CHECK_RESULT(hr);
						}
					}
					QueueFence = CommandQueuePrivateData.pFence;
					RESIDENCY_CHECK(QueueFence != nullptr);
				}

				return hr;
			}

			HRESULT SignalFence(ID3D12CommandQueue *Queue, Internal::Fence *QueueFence)
			{
				// When this fence is passed it is safe to evict the resources used in the list just submitted
				HRESULT hr = QueueFence->GPUSignal(Queue);
				QueueFence->Increment();

				if (SUCCEEDED(hr))
				{
					hr = EnqueueSyncPoint();
					RESIDENCY_CHECK_RESULT(hr);
				}

				CurrentSyncPointGeneration++;
				return hr;
			}

			HRESULT ExecuteSubset(ID3D12CommandQueue* Queue, ID3D12CommandList** CommandLists, ResidencySet** ResidencySets, UINT32 Count)
			{
				HRESULT hr = S_OK;

				DXGI_QUERY_VIDEO_MEMORY_INFO LocalMemory;
				ZeroMemory(&LocalMemory, sizeof(LocalMemory));
				GetCurrentBudget(&LocalMemory, DXGI_MEMORY_SEGMENT_GROUP_LOCAL);

				DXGI_QUERY_VIDEO_MEMORY_INFO NonLocalMemory;
				ZeroMemory(&NonLocalMemory, sizeof(NonLocalMemory));
				GetCurrentBudget(&NonLocalMemory, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL);

				UINT64 TotalSizeNeeded = 0;

				UINT32 MaxObjectsReferenced = 0;
				for (UINT32 i = 0; i < Count; i++)
				{
					if (ResidencySets[i])
					{
						if (ResidencySets[i]->IsOpen)
						{
							// Residency Sets must be closed before execution just like Command Lists
							return E_INVALIDARG;
						}
						MaxObjectsReferenced += ResidencySets[i]->CurrentSetSize;
					}
				}

				// Create a set to gather up all unique resources required by this call
				ResidencySet* pMasterSet = new ResidencySet();
				if (pMasterSet == nullptr || pMasterSet->Initialize(pSyncManager, MaxObjectsReferenced) == false)
				{
					return E_OUTOFMEMORY;
				}

				hr = pMasterSet->Open();
				if (FAILED(hr))
				{
					return hr;
				}

				// For each residency set
				for (UINT32 i = 0; i < Count; i++)
				{
					if (ResidencySets[i])
					{
						// For each object in this set
						for (INT32 x = 0; x < ResidencySets[i]->CurrentSetSize; x++)
						{
							if (pMasterSet->Insert(ResidencySets[i]->ppSet[x]))
							{
								TotalSizeNeeded += ResidencySets[i]->ppSet[x]->Size;
							}
						}
					}
				}
				// Close this set to free it's slot up for the app
				hr = pMasterSet->Close();
				if (FAILED(hr))
				{
					return hr;
				}

				// This set of commandlists can't possibly fit within the budget, they need to be split up. If the number of command lists is 1 there is
				// nothing we can do
				if (Count > 1 && TotalSizeNeeded > LocalMemory.Budget + NonLocalMemory.Budget)
				{
					delete(pMasterSet);

					// Recursively try to find a small enough set to fit in memory
					const UINT32 Half = Count / 2;
					const HRESULT LowerHR = ExecuteSubset(Queue, CommandLists, ResidencySets, Half);
					const HRESULT UpperHR = ExecuteSubset(Queue, &CommandLists[Half], &ResidencySets[Half], Count - Half);

					return (LowerHR == S_OK && UpperHR == S_OK) ? S_OK : E_FAIL;
				}


				Internal::Fence* QueueFence = nullptr;
				hr = GetFence(Queue, QueueFence);

				if (SUCCEEDED(hr))
				{
					// The following code must be atomic so that things get ordered correctly

					Internal::ScopedLock Lock(&ExecutionCS);
					// Evict or make resident all of the objects we identified above.
					// This will run on an async thread, allowing the current to continue while still blocking the GPU if required
					hr = EnqueueAsyncWork(pMasterSet, AsyncThreadFence.FenceValue, CurrentSyncPointGeneration);
#if RESIDENCY_SINGLE_THREADED
					AsyncWorkload* pWorkload = DequeueAsyncWork();
					ProcessPagingWork(pWorkload);
#endif

					// If there are some things that need to be made resident we need to make sure that the GPU
					// doesn't execute until the async thread signals that the MakeResident call has returned.
					if (SUCCEEDED(hr))
					{
						hr = AsyncThreadFence.GPUWait(Queue);
						AsyncThreadFence.Increment();
					}

					Queue->ExecuteCommandLists(Count, CommandLists);

					if (SUCCEEDED(hr))
					{
						hr = SignalFence(Queue, QueueFence);
					}
				}
				return hr;
			}

			struct AsyncWorkload
			{
				AsyncWorkload() :
					pMasterSet(nullptr),
					FenceValueToSignal(0),
					SyncPointGeneration(0)
				{}

				UINT64 SyncPointGeneration;

				// List of objects to make resident
				ResidencySet* pMasterSet;

				// The GPU will wait on this value so that it doesn't execute until the objects are made resident
				UINT64 FenceValueToSignal;
			};

			SIZE_T AsyncWorkQueueSize;
			AsyncWorkload* AsyncWorkQueue;

			HANDLE AsyncWorkEvent;
			HANDLE AsyncWorkThread;
			Internal::CriticalSection AsyncWorkMutex;
			volatile bool FinishAsyncWork;
			volatile SIZE_T CurrentAsyncWorkloadHead;
			volatile SIZE_T CurrentAsyncWorkloadTail;

			static unsigned long WINAPI AsyncThreadStart(void* pData)
			{
				ResidencyManagerInternal* pManager = (ResidencyManagerInternal*)pData;

				while (1)
				{
					AsyncWorkload* pWork = pManager->DequeueAsyncWork();

					while (pWork)
					{
						// Submit the work
						pManager->ProcessPagingWork(pWork);
						if (SetEvent(pManager->AsyncThreadWorkCompletionEvent) == false)
						{
							RESIDENCY_CHECK_RESULT(HRESULT_FROM_WIN32(GetLastError()));
						}

						// Get more work
						pWork = pManager->DequeueAsyncWork();
					}

					//Wait until there is more work do be done
					WaitForSingleObject(pManager->AsyncWorkEvent, INFINITE);
					if (ResetEvent(pManager->AsyncWorkEvent) == false)
					{
						RESIDENCY_CHECK_RESULT(HRESULT_FROM_WIN32(GetLastError()));
					}

					if (pManager->FinishAsyncWork)
					{
						return 0;
					}
				}

				return 0;
			}

			// This will be run from a worker thread and will emulate a software queue for making gpu resources resident or evicted.
			// The GPU will be synchronized by this queue to ensure that it never executes using an evicted resource.
			void ProcessPagingWork(AsyncWorkload* pWork)
			{
				Internal::DeviceWideSyncPoint* FirstUncompletedSyncPoint = DequeueCompletedSyncPoints();

				// Use a union so that we only need 1 allocation
				union ResidentScratchSpace
				{
					ManagedObject* pManagedObject;
					ID3D12Pageable* pUnderlying;
				};

				ResidentScratchSpace* pMakeResidentList = nullptr;
				UINT32 NumObjectsToMakeResident = 0;

				ID3D12Pageable** pEvictionList = nullptr;
				UINT32 NumObjectsToEvict = 0;

				// the size of all the objects which will need to be made resident in order to execute this set.
				UINT64 SizeToMakeResident = 0;

				LARGE_INTEGER CurrentTime;
				QueryPerformanceCounter(&CurrentTime);

				{
					// A lock must be taken here as the state of the objects will be altered
					Internal::ScopedLock Lock(&Mutex);

					pMakeResidentList = new ResidentScratchSpace[pWork->pMasterSet->CurrentSetSize];
					pEvictionList = new ID3D12Pageable*[LRU.NumResidentObjects];

					// Mark the objects used by this command list to be made resident
					for (INT32 i = 0; i < pWork->pMasterSet->CurrentSetSize; i++)
					{
						ManagedObject*& pObject = pWork->pMasterSet->ppSet[i];
						// If it's evicted we need to make it resident again
						if (pObject->ResidencyStatus == ManagedObject::RESIDENCY_STATUS::EVICTED)
						{
							pMakeResidentList[NumObjectsToMakeResident++].pManagedObject = pObject;
							LRU.MakeResident(pObject);

							SizeToMakeResident += pObject->Size;
						}

						// Update the last sync point that this was used on
						pObject->LastGPUSyncPoint = pWork->SyncPointGeneration;

						pObject->LastUsedTimestamp = CurrentTime.QuadPart;
						LRU.ObjectReferenced(pObject);
					}

					DXGI_QUERY_VIDEO_MEMORY_INFO LocalMemory;
					ZeroMemory(&LocalMemory, sizeof(LocalMemory));
					GetCurrentBudget(&LocalMemory, DXGI_MEMORY_SEGMENT_GROUP_LOCAL);

					UINT64 EvictionGracePeriod = GetCurrentEvictionGracePeriod(&LocalMemory);
					LRU.TrimAgedAllocations(FirstUncompletedSyncPoint, pEvictionList, NumObjectsToEvict, CurrentTime.QuadPart, EvictionGracePeriod);

					if (NumObjectsToEvict)
					{
						RESIDENCY_CHECK_RESULT(Device->Evict(NumObjectsToEvict, pEvictionList));
						NumObjectsToEvict = 0;
					}

					if (NumObjectsToMakeResident)
					{
						UINT32 ObjectsMadeResident = 0;
						UINT32 MakeResidentIndex = 0;
						while (true)
						{
							ZeroMemory(&LocalMemory, sizeof(LocalMemory));

							GetCurrentBudget(&LocalMemory, DXGI_MEMORY_SEGMENT_GROUP_LOCAL);
							DXGI_QUERY_VIDEO_MEMORY_INFO NonLocalMemory;
							ZeroMemory(&NonLocalMemory, sizeof(NonLocalMemory));
							GetCurrentBudget(&NonLocalMemory, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL);

							INT64 TotalUsage = LocalMemory.CurrentUsage + NonLocalMemory.CurrentUsage;
							INT64 TotalBudget = LocalMemory.Budget + NonLocalMemory.Budget;

							INT64 AvailableSpace = TotalBudget - TotalUsage;

							UINT64 BatchSize = 0;
							UINT32 NumObjectsInBatch = 0;
							UINT32 BatchStart = MakeResidentIndex;

							HRESULT hr = S_OK;
							if (AvailableSpace > 0)
							{
								for (UINT32 i = MakeResidentIndex; i < NumObjectsToMakeResident; i++)
								{
									// If we try to make this object resident, will we go over budget?
									if (BatchSize + pMakeResidentList[i].pManagedObject->Size > UINT64(AvailableSpace))
									{
										// Next time we will start here
										MakeResidentIndex = i;
										break;
									}
									else
									{
										BatchSize += pMakeResidentList[i].pManagedObject->Size;
										NumObjectsInBatch++;
										ObjectsMadeResident++;

										pMakeResidentList[i].pUnderlying = pMakeResidentList[i].pManagedObject->pUnderlying;
									}
								}

								hr = Device->MakeResident(NumObjectsInBatch, &pMakeResidentList[BatchStart].pUnderlying);
								if (SUCCEEDED(hr))
								{
									SizeToMakeResident -= BatchSize;
								}
							}

							if (FAILED(hr) || ObjectsMadeResident != NumObjectsToMakeResident)
							{
								ManagedObject* pResidentHead = LRU.GetResidentListHead();

								// Get the next sync point to wait for
								FirstUncompletedSyncPoint = DequeueCompletedSyncPoints();

								// If there is nothing to trim OR the only objects 'Resident' are the ones about to be used by this execute.
								if (pResidentHead == nullptr ||
									pResidentHead->LastGPUSyncPoint >= pWork->SyncPointGeneration ||
									FirstUncompletedSyncPoint == nullptr)
								{
									// Make resident the rest of the objects as there is nothing left to trim
									UINT32 NumObjects = NumObjectsToMakeResident - ObjectsMadeResident;

									// Gather up the remaining underlying objects
									for (UINT32 i = MakeResidentIndex; i < NumObjectsToMakeResident; i++)
									{
										pMakeResidentList[i].pUnderlying = pMakeResidentList[i].pManagedObject->pUnderlying;
									}

									hr = Device->MakeResident(NumObjects, &pMakeResidentList[MakeResidentIndex].pUnderlying);
									if (FAILED(hr))
									{
										// TODO: What should we do if this fails? This is a catastrophic failure in which the app is trying to use more memory
										//       in 1 command list than can possibly be made resident by the system.
										RESIDENCY_CHECK_RESULT(hr);
									}
									break;
								}

								UINT64 GenerationToWaitFor = FirstUncompletedSyncPoint->GenerationID;

								// We can't wait for the sync-point that this work is intended for
								if (GenerationToWaitFor == pWork->SyncPointGeneration)
								{
									RESIDENCY_CHECK(GenerationToWaitFor >= 0);
									GenerationToWaitFor -= 1;
								}
								// Wait until the GPU is done
								WaitForSyncPoint(GenerationToWaitFor);

								LRU.TrimToSyncPointInclusive(TotalUsage + INT64(SizeToMakeResident), TotalBudget, pEvictionList, NumObjectsToEvict, GenerationToWaitFor);

								RESIDENCY_CHECK_RESULT(Device->Evict(NumObjectsToEvict, pEvictionList));
							}
							else
							{
								// We made everything resident, mission accomplished
								break;
							}
						}
					}

					delete[](pMakeResidentList);
					delete[](pEvictionList);
				}

				// Tell the GPU that it's safe to execute since we made things resident
				RESIDENCY_CHECK_RESULT(AsyncThreadFence.pFence->Signal(pWork->FenceValueToSignal));

				delete(pWork->pMasterSet);
				pWork->pMasterSet = nullptr;
			}
			// The Enqueue and Dequeue Async Work functions are threadsafe as there is only 1 producer and 1 consumer, if that changes
			// Synchronisation will be required
			HRESULT EnqueueAsyncWork(ResidencySet* pMasterSet, UINT64 FenceValueToSignal, UINT64 SyncPointGeneration)
			{
				// We can't get too far ahead of the worker thread otherwise huge hitches occur
				while ((CurrentAsyncWorkloadTail - CurrentAsyncWorkloadHead) >= MaxSoftwareQueueLatency)
				{
					WaitForSingleObject(AsyncThreadWorkCompletionEvent, INFINITE);
				}

				RESIDENCY_CHECK(CurrentAsyncWorkloadTail >= CurrentAsyncWorkloadHead);

				const SIZE_T currentIndex = CurrentAsyncWorkloadTail % AsyncWorkQueueSize;
				AsyncWorkQueue[currentIndex].pMasterSet = pMasterSet;
				AsyncWorkQueue[currentIndex].FenceValueToSignal = FenceValueToSignal;
				AsyncWorkQueue[currentIndex].SyncPointGeneration = SyncPointGeneration;

				CurrentAsyncWorkloadTail++;
				if (SetEvent(AsyncWorkEvent) == false)
				{
					return HRESULT_FROM_WIN32(GetLastError());
				}

				return S_OK;
			}

			AsyncWorkload* DequeueAsyncWork()
			{
				if (CurrentAsyncWorkloadHead == CurrentAsyncWorkloadTail)
				{
					return nullptr;
				}

				const SIZE_T currentHead = CurrentAsyncWorkloadHead % AsyncWorkQueueSize;
				AsyncWorkload* pWork = &AsyncWorkQueue[currentHead];

				CurrentAsyncWorkloadHead++;
				return pWork;
			}

			void GetCurrentBudget(DXGI_QUERY_VIDEO_MEMORY_INFO* InfoOut, DXGI_MEMORY_SEGMENT_GROUP Segment)
			{
				RESIDENCY_CHECK_RESULT(Adapter->QueryVideoMemoryInfo(NodeIndex, Segment, InfoOut));
			}

			HRESULT EnqueueSyncPoint()
			{
				Internal::ScopedLock Lock(&AsyncWorkMutex);

				Internal::DeviceWideSyncPoint* pPoint = Internal::DeviceWideSyncPoint::CreateSyncPoint(NumQueuesSeen, CurrentSyncPointGeneration);
				if (pPoint == nullptr)
				{
					return E_OUTOFMEMORY;
				}

				UINT32 i = 0;
				LIST_ENTRY* pFenceEntry = QueueFencesListHead.Flink;
				// Record the current state of each queue we track into this sync point
				while (pFenceEntry != &QueueFencesListHead)
				{
					Internal::Fence* pFence = CONTAINING_RECORD(pFenceEntry, Internal::Fence, ListEntry);
					pFenceEntry = pFenceEntry->Flink;

					pPoint->pQueueSyncPoints[i].pFence = pFence;
					pPoint->pQueueSyncPoints[i].LastUsedValue = pFence->FenceValue - 1;//Minus one as we want the last submitted

					i++;
				}

				Internal::InsertTailList(&InFlightSyncPointsHead, &pPoint->ListEntry);

				return S_OK;
			}

			// Returns a pointer to the first synch point which is not completed
			Internal::DeviceWideSyncPoint* DequeueCompletedSyncPoints()
			{
				Internal::ScopedLock Lock(&AsyncWorkMutex);

				while (Internal::IsListEmpty(&InFlightSyncPointsHead) == false)
				{
					Internal::DeviceWideSyncPoint* pPoint =
						CONTAINING_RECORD(InFlightSyncPointsHead.Flink, Internal::DeviceWideSyncPoint, ListEntry);

					if (pPoint->IsCompleted())
					{
						Internal::RemoveHeadList(&InFlightSyncPointsHead);
						delete pPoint;
					}
					else
					{
						return pPoint;
					}
				}

				return nullptr;
			}

			void WaitForSyncPoint(UINT64 SyncPointID)
			{
				Internal::ScopedLock Lock(&AsyncWorkMutex);

				LIST_ENTRY* pPointEntry = InFlightSyncPointsHead.Flink;
				while (pPointEntry != &InFlightSyncPointsHead)
				{
					Internal::DeviceWideSyncPoint* pPoint =
						CONTAINING_RECORD(InFlightSyncPointsHead.Flink, Internal::DeviceWideSyncPoint, ListEntry);

					if (pPoint->GenerationID > SyncPointID)
					{
						// this point is already done
						return;
					}
					else if (pPoint->GenerationID < SyncPointID)
					{
						// Keep popping off until we find the one to wait on
						Internal::RemoveHeadList(&InFlightSyncPointsHead);
						delete(pPoint);
					}
					else
					{
						pPoint->WaitForCompletion(CompletionEvent);
						Internal::RemoveHeadList(&InFlightSyncPointsHead);
						delete(pPoint);
						return;
					}
				}
			}

			// Generate a result between the minimum period and the maximum period based on the current
			// local memory pressure. I.e. when memory pressure is low, objects will persist longer before
			// being evicted.
			UINT64 GetCurrentEvictionGracePeriod(DXGI_QUERY_VIDEO_MEMORY_INFO* LocalMemoryState)
			{
				// 1 == full pressure, 0 == no pressure
				double Pressure = (double(LocalMemoryState->CurrentUsage) / double(LocalMemoryState->Budget));
				Pressure = RESIDENCY_MIN(Pressure, 1.0);

				if (Pressure > cTrimPercentageMemoryUsageThreshold)
				{
					// Normalize the pressure for the range 0 to cTrimPercentageMemoryUsageThreshold
					Pressure = (Pressure - cTrimPercentageMemoryUsageThreshold) / (1.0 - cTrimPercentageMemoryUsageThreshold);

					// Linearly interpolate between the min period and the max period based on the pressure
					return UINT64((MaxEvictionGracePeriodTicks - MinEvictionGracePeriodTicks) * (1.0 - Pressure)) + MinEvictionGracePeriodTicks;
				}
				else
				{
					// Essentially don't trim at all
					return MAXUINT64;
				}
			}

			LIST_ENTRY QueueFencesListHead;
			UINT32 NumQueuesSeen;
			Internal::Fence AsyncThreadFence;

			LIST_ENTRY InFlightSyncPointsHead;
			UINT64 CurrentSyncPointGeneration;

			HANDLE CompletionEvent;
			HANDLE AsyncThreadWorkCompletionEvent;

			ID3D12Device* Device;
			// NOTE: This is an index not a mask. The majority of D3D12 uses bit masks to identify a GPU node whereas DXGI uses 0 based indices.
			UINT NodeIndex;
			IDXGIAdapter3* Adapter;
			Internal::LRUCache LRU;

			Internal::CriticalSection Mutex;

			Internal::CriticalSection ExecutionCS;

			const bool cStartEvicted;

			const float cMinEvictionGracePeriod;
			UINT64 MinEvictionGracePeriodTicks;
			const float cMaxEvictionGracePeriod;
			UINT64 MaxEvictionGracePeriodTicks;
			// When the app is using more than this % of its budgeted local VidMem trimming will occur
			// (valid between 0.0 - 1.0)
			const float cTrimPercentageMemoryUsageThreshold;

			UINT32 MaxSoftwareQueueLatency;
			INT64 ResidencyManagerUniqueID;

			SyncManager* pSyncManager;
		};
	}

	class ResidencyManager
	{
	public:
		ResidencyManager() :
			Manager(&SyncManager)
		{
		}

		// NOTE: DeviceNodeIndex is an index not a mask. The majority of D3D12 uses bit masks to identify a GPU node whereas DXGI uses 0 based indices.
		FORCEINLINE HRESULT Initialize(ID3D12Device* ParentDevice, UINT DeviceNodeIndex, IDXGIAdapter3* ParentAdapter, UINT32 MaxLatency)
		{
			return Manager.Initialize(ParentDevice, DeviceNodeIndex, ParentAdapter, MaxLatency);
		}

		FORCEINLINE void Destroy()
		{
			Manager.Destroy();
		}

		FORCEINLINE void BeginTrackingObject(ManagedObject* pObject)
		{
			Manager.BeginTrackingObject(pObject);
		}

		FORCEINLINE void EndTrackingObject(ManagedObject* pObject)
		{
			Manager.EndTrackingObject(pObject);
		}

		HRESULT GetCurrentGPUSyncPoint(ID3D12CommandQueue* Queue, UINT64 *pCurrentGPUSyncPoint)
		{
			return Manager.GetCurrentGPUSyncPoint(Queue, pCurrentGPUSyncPoint);
		}

		// One residency set per command-list
		FORCEINLINE HRESULT ExecuteCommandLists(ID3D12CommandQueue* Queue, ID3D12CommandList** CommandLists, ResidencySet** ResidencySets, UINT32 Count)
		{
			return Manager.ExecuteCommandLists(Queue, CommandLists, ResidencySets, Count);
		}

		FORCEINLINE ResidencySet* CreateResidencySet()
		{
			ResidencySet* pSet = new ResidencySet();

			if (pSet)
			{
				pSet->Initialize(&SyncManager);
			}
			return pSet;
		}

		FORCEINLINE void DestroyResidencySet(ResidencySet* pSet)
		{
			delete(pSet);
		}

	private:
		Internal::ResidencyManagerInternal Manager;
		Internal::SyncManager SyncManager;
	};
};
